<?php
define ('TE_TYPE', 0);
define ('TE_VALUE', 1);

define ('TE_TYPE_STRING', 0);
define ('TE_TYPE_TEMPLATE', 1);
define ('TE_TYPE_VARIABLE', 2);
define ('TE_TYPE_PHP_SCRIPT', 3);
define ('TE_TYPE_EXPR', 4);

class TplCompiler
/**
  Компилятор шаблонного движка. Создаётся внутри TE, руками пользоваться не должен.

  TemplateEngineCompiler( $tpl )

  ---------

  // Работа с шаблонами

  * TemplateCompile( $skin_name, $tpl_name, $file_from, $file_to ) -- возвращает true, если удачно
       - $skin_name -- в какую шкуру кэшировать
       - $tpl_name -- имя файла шаблона без расширения, например "news" для "news.html"
       - $file_from/$file_to -- полные имена файлов оригинала и кэша

  * _TemplateRead( $file_name ) -- возвращает array( name => content ), умолчательная передаётся
                                   через "@"=>...
  * _TemplateCompile( $content ) -- компилирует "контент" одного шаблона
  * _ParseActionParams( $content )     -- парсит параметры action в массив
  * _ImplodeActionParams( $params )    -- превращает массив в php-строку, генерирующую этот массив
  * _TemplateBuildFunction( $content ) -- строит тело функции вокруг $content
  * _CheckWritable( $file_to ) -- проверяет права на запись в файл $file_to,
                    если прав нет, то проверяет права на запись в содержащую директорию.
  * _ConstructGetValue($field_name) -- конструирует обращение к объекту $_ внутри компилята.
                    Обращение как к автомассиву, хэшу или объекту.

  // Работа с Actions

  * ActionCompile( $skin_name, $tpl_name, $file_from, $file_to ) -- возвращает true, если удачно
       - $skin_name -- в какую шкуру кэшировать
       - $action_name -- имя файла экшена без расширения, например "message" для "message.html"
       - $file_from/$file_to -- полные имена файлов оригинала и кэша


  ---------

  Необходимые параметры в $tpl, описывающие правила разбора шаблонов:

  $tpl->tpl_prefix = "{{";
  $tpl->tpl_postfix = "}}";
  $tpl->tpl_construct_action   = "!";    // {{!text Test}}
  $tpl->tpl_construct_action2  = "!!";   // {{!!text}}Test{{!!/text}}
  $tpl->tpl_construct_if       = "?";    // {{?var}} or {{?!var}}
  $tpl->tpl_construct_ifelse   = "?:";   // {{?:}}
  $tpl->tpl_construct_object   = "#.";   // {{#obj.property}}
  $tpl->tpl_construct_tplt     = "TPL:"; // {{TPL:Name}}...{{/TPL:Name}}
  $tpl->tpl_construct_comment  = "#";    // <!-- # persistent comment -->
  $tpl->tpl_construct_for       = "!for";    // {{!for items do=template}}

  // lucky:
  $tpl->tpl_construct_standard_camelCase   = True;   // $o->SomeValue(), иначе $o->some_value()
  $tpl->tpl_construct_standard_getter_prefix   = 'get';   // $o->getSomeValue(), иначе $o->SomeValue()

  $tpl->shortcuts = array(
      "=>" => array("=", " typografica=1"),
      "=<" => array("=", " strip_tags=1"),
      "+>" => array("+", " typografica=1"),
      "+<" => array("+", " strip_tags=1"),
      "@" => "!include @",
      "=" => "!text ",
      "+" => "!message ",
                        );

=============================================================== v.1 (kuso@npj, zharik@npj)
*/
{

	/**
	 * Ссылка на объект шаблонного двигателя
	 *
	 * @var RL_TemplateEngine
	 */
  public $tpl;

	/**
	 * tpl_stack стек имен парсящихся шаблонов.
	 *
	 * @var array
	 * @access private
	 */
	private $tpl_stack = array();

  /**
   * Конструктор
   *
   * @param RL_TemplateEngine $tpl
   */
  public function __construct(Tpl $tpl)
  {
    $this->tpl = $tpl;
    // compiler meta regexp
    $this->long_regexp =
         "/(".
              "(".$this->tpl->tpl_prefix.$this->tpl->tpl_construct_action2.
                  "(.+?)".$this->tpl->tpl_postfix.
                  ".*?".
                  $this->tpl->tpl_prefix."\/".$this->tpl->tpl_construct_action2.
                  "(\\3)?".$this->tpl->tpl_postfix.")".         // loooong standing {{!!xxx}}yyy{{/!!}}
          "|".
              "(".$this->tpl->tpl_prefix.".+?".$this->tpl->tpl_postfix.")". // single standing {{zzz}}
          ")/ims";

    $this->single_regexp =
         "/^".$this->tpl->tpl_prefix."(.+?)".$this->tpl->tpl_postfix."$/ims";
    $this->double_regexp =
         "/^".$this->tpl->tpl_prefix.$this->tpl->tpl_construct_action2.
              "(.+?)".$this->tpl->tpl_postfix.
              "(.*?)".
              $this->tpl->tpl_prefix."\/".$this->tpl->tpl_construct_action2.
              "(\\1)?".$this->tpl->tpl_postfix."$/si";
    // single regexps *ab_c:de.0.fgh123i
    $this->object_regexp =
		 '((?:[_[:alpha:]*][_[:alnum:]:]*|^\\$[*]|^/+)'
		 .		'(?:'
		 .			 preg_quote($this->tpl->tpl_construct_object{1})
		 .			 '[_[:alnum:]]+'
		 .		 ')*'
		 .')';
    $this->action_regexp =
         "/^".$this->tpl->tpl_construct_action."(.+)$/i";
    $this->tpl_string_regexp = '/^([\'"])([^\\1]*)(\\1)$/i';
    $this->tpl_expr_regexp = '/^(\()([^()]+)(?(1)\))$/i';
	 $this->tpl_arg_regexp = '/^'.preg_quote($this->tpl->tpl_arg_prefix, '/')
		 .'(.+)'.preg_quote($this->tpl->tpl_arg_postfix, '/').'$/i';

  }

  // Работа с шаблонами

	function TemplateCompile( $skin_name, $tpl_name, $tpl_name_for_cache, $file_from, $file_to ) // -- возвращает true, если удачно
	{
		if (!file_exists($file_from))
			error( "TPL::TemplateCompile: (". $tpl_name.  ") file_from *".$file_from."* not found" );

		trace( "[TemplateCompiler] (". $tpl_name.  ") compile" );
		$this->tpl_stack[] = $tpl_name;
		// 1. разобрать файл на кусочки
		if (!$pieces = $this->_TemplateRead( $tpl_name, $file_from ))
			return false;

		// 2. скомпилировать кусочки
		$this->_template_name = $tpl_name;

		$functions = array();
		foreach( $pieces as $k=>$v ) {
			// объединяем одноимянные шаблоны в одну функцию if () elseif () ... else
			// если кусок один -- тогда возвращаем без условий
			// на выходе $fbody - тело функции
			$fbody = '';
			$single_pattern = False;
			foreach ($v as $vv) {
				$args = $vv[0];
				$body = $vv[1];
				if (empty($fbody) && empty($args)) {
					// мы тут первый и последний раз. это {{:tpl}}
					$fbody = $body; $single_pattern = True; break;
				} elseif (empty($fbody)) {
					// мы тут первый раз. это {{:tpl *whatsup}}
					$fbody .= $this->tpl->tpl_prefix
						.$this->tpl->tpl_construct_if
						.' '
						.$args
						.$this->tpl->tpl_postfix
						.$body;
				} else {
					// это {{:tpl *whatsnext}}
					$fbody .= $this->tpl->tpl_prefix
						.$this->tpl->tpl_construct_ifelse
						.$args
						.$this->tpl->tpl_postfix
						.$body;
					if (empty($args)) break; // нет аргументов -- это безусловный шаблон
					// шаблоны после можно пропустить
				}
			}
			if (!$single_pattern) {
				$fbody .= $this->tpl->tpl_prefix
					."/".$this->tpl->tpl_construct_if
					.$this->tpl->tpl_postfix;
			}
			$functions[ $this->tpl->tpl_template_prefix.$skin_name.
				$this->tpl->tpl_template_sepfix.$tpl_name_for_cache.
				(($k=="@")?"":$this->tpl->tpl_template_sepfix.$k) ] =
				$this->_TemplateBuildFunction( $this->_TemplateCompile($fbody) );
		}

		// 3. собрать функции
		ob_start();
		echo "<"."?php // made by Rockette. do not change manually, you mortal.\n\n";
		// пишем регистратор функций как плагинов в шаблонном движке.
		$tpl_names = array_keys($pieces); $func_names = array_keys($functions);
		for($i=0,$c=sizeof($tpl_names);$i<$c;$i++) {
			$_tpl_name = $tpl_name.'.html'.($tpl_names[$i] === '@' ? '' : ':'.$tpl_names[$i]);
			// $this это $tpl
			echo "\$this->register_plugin_action(array('{$_tpl_name}','{$func_names[$i]}'));\n";
		}
		// пишем функции
		foreach($functions as $fname=>$content)
		{
			echo "function $fname(&\$params, \$tpl)";
			echo $content;
		}
		echo '?'.'>';

		// 4. можем ли записывать файл?
		$this->_CheckWritable($file_to);

		// 5. пишем в файл
		$fp = fopen( $file_to ,"w");
		fwrite($fp, $this->output_filter(ob_get_clean()));
		fclose($fp);

		array_pop($this->tpl_stack);

		return true;
	}

  //zharik
  /*
    Код перенесён и адаптирован с TemplateCompile.
    Я старался, что бы эти две функции выглядели похоже, поэтому оставил массив $functions и
    пробег по нему при склейке файла. Имхо, потерь производительности при этом почти никаких.
  */
  function ActionCompile( $skin_name, $action_name, $action_name_for_cache, $file_from, $file_to ) // -- возвращает true, если удачно
  {
    if (!file_exists($file_from))
      error( "TPL::ActionCompile: file_from *".$file_name."* not found" );

    //1. обернуть экшен как функицю
    //построение стандартного окружение
    //$enviroment .= implode( '', file( $this->rh->FindScript_('handlers','_enviroment') ) )."\n";
# FIXME : lucky : 2008-03-09
# хотя концепция $enviroment интересная,
# нужна-ли она в общем случае -- мне сейчас не понятно.
# т.к. код создает проблемы с использованием Tpl вне Rocket,
# закомментил до лучих времён.
#
# использование $rh это особенность rocket'овских экшенов,
# но $rh им можно передавать и в общем порядке -- через домен шаблонных переменных:
#   $tpl = new Tpl(...);
#   $tpl->set('rh', $rh);
#
# в rocket-экшене:
#   $rh = $tpl->get('rh');
#   и далее всё как обычно.
#
# //
# $enviroment = "\n<"."?php \$rh = \$tpl->rh; ?".">\n";
		$enviroment = '';
    //собственно оборачивание
    $functions = array();
    $functions[ $this->tpl->tpl_action_prefix.$skin_name.
                $this->tpl->tpl_template_sepfix.$action_name_for_cache ] =
                $this->_TemplateBuildFunction( $enviroment.
                  preg_replace('#/\*.*?\*/#s','',@file_get_contents($file_from))
                );

    // 2. собрать функции
		ob_start();
    echo "<"."?php // made by Rockette. do not change manually, you mortal.\n\n";
    foreach($functions as $name=>$content)
    {
      echo 'function '.$name.' (&$params, $tpl)'."\n";
			echo $content;
      echo "\n";
    }
    echo "?".">";

    // 3. можем ли записывать файл?
    $this->_CheckWritable($file_to);

    // 4. пишем в файл
    $fp = fopen( $file_to ,"w");
		fwrite($fp, $this->output_filter(ob_get_clean()));
    fclose($fp);

    return true;
  }

	function getCurrentTemplateName()
	{
		return count($this->tpl_stack) > 0 ? end($this->tpl_stack) : NULL;
	}

	public function getTemplateContent ($file_name)
	{
		// A. error_checking
		// -- .html templates
		$pi = pathinfo( $file_name );
		if ($pi["extension"] != "html")
			error("TPL::_TemplateRead: [mooing duck alert] not .html template found @ $file_name!");
		// {{/TPL}}

		// 0. get contents
		$contents = @file($file_name);
		if (!$contents){
			error("File $file_name not found, sorry");
			return false;
		}

		$contents = implode("",$contents);
		return $contents;
	} // end of method getTemplateContents

	public function _explodeTemplate ($tpl_name, $contents)
	{
		$this->tpl_stack[] = $tpl_name;

		if (preg_match( "/".$this->tpl->tpl_prefix."\/".
			$this->tpl->tpl_construct_tplt.
			$this->tpl->tpl_postfix."/si", $contents, $matches))
			error("TPL::_TemplateRead: [mooing duck alert] {{/TPL}} found!");


		// 1. strip comments
		$contents = preg_replace( "/(\s*)<!--(".
			"( )".
			"|".
			"( [^".$this->tpl->tpl_construct_comment."].*?)".
			"|".
			"([^ ".$this->tpl->tpl_construct_comment."].*?)".
			")-->(\s*)/msi",
			"", $contents );


		// and then strip "contstructness" from comments
		$contents = preg_replace( "/(\s*)<!--( )?#(.*?)-->/msi",
			"<!--$2$3-->", $contents );

		//zharik: protect ourselfs from '<?' in templates
		$contents = str_replace( '<'.'?', '<<?'.'php ; ?'.'>?', $contents );

		// lucky: наследование шаблонов
		// {{#extends @path/to/base.html}}
		$is_extends = NULL;
		$re = $this->tpl->tpl_prefix.'#extends'.'\s+@(.+?)\\.html\s*'.$this->tpl->tpl_postfix;
		if (preg_match('/'. $re .'/i', $contents, $matches)) {
			$is_extends = TRUE;
			$res = $this->_TemplateRead($tpl_name, $this->tpl->findTemplate($matches[1]));
			$contents = str_replace( $matches[0], '', $contents );
		} else {
			$is_extends = FALSE;
			$res = array();
		}

		// 2. grep all {{TPL:..}} & replace `em by !includes
		//$include_prefix = $this->tpl->tpl_prefix.$this->tpl->tpl_construct_action."include ";
		$stack     = array( $contents );
		$stackname = array( "@" );
		$stackargs = array( '' );
		$stackpos = 0;

		/* ru@jetstyle чтобы шаблоны можно было метить облегчённо */
		$tpl_construct_tplt_re = '('.$this->tpl->tpl_construct_tplt.'|'.$this->tpl->tpl_construct_tplt2.')';
		//разрезаем на подшаблоны
		while ($stackpos < sizeof($stack) )
		{
			$data = $stack[$stackpos];
			$c =preg_match_all( "/".$this->tpl->tpl_prefix.                      // {{
				$tpl_construct_tplt_re.'([A-Za-z0-9_]+)(?:\s+(.+?))?'.    // TPL:foo *cond
					$this->tpl->tpl_postfix."(.*?)".                  // }}
				$this->tpl->tpl_prefix."\/".
													/*$this->tpl->tpl_construct_tplt*/"\\1\\2".
													$this->tpl->tpl_postfix."/si",
													$data, $matches, PREG_SET_ORDER  );
				foreach( $matches as $match )
				{
					//$sub = $tpl_name.".html:".$match[1];
					//$data = str_replace( $match[0], $include_prefix.$sub.$this->tpl->tpl_postfix, $data );
					$data = str_replace( $match[0], '', $data ); // ru@jetstyle

					$stack[]     = $match[4]; // контент внутри шаблона
					$stackname[] = $match[2]; // имя шаблона
					$stackargs[] = $match[3]; // аргументы шаблона
					// lucky: наследование: override'им родительские определения подшаблонов
					if ($is_extends /*&& isset($res[$match[2]])*/) {
						unset($res[$match[2]]);
					}
				}

				$stack[$stackpos] = $data;
				$stackpos++;
		}

		if ($is_extends) {
			// lucky: что использовать в качестве основного шаблона: себя или родителя?
			//   если в шаблоне остался неразобранный текст
			//   используем себя
			if (trim($stack[0]) !== '') {
				//$stack[0] .= $res['@'][0][1]; // HACK : 2007-09-08 : lucky
				// вставляет основной шаблон перед базовым (aka decorator)
				// если убрать эту строчку, то будет замещать (чистый override)
				unset($res['@']);
			} else {
				// иначе используем родителя $res['@'] (себя удалим)
				unset($stack[0], $stackname[0], $stackargs[0]);
			}
		}

		// pack results
		foreach( $stack as $k=>$v ) {
			$res[ $stackname[$k] ][] = array($stackargs[$k], $v);
		}

		array_pop($this->tpl_stack);
		return $res;
	} // end of method _explodeTemplate

	public function _TemplateRead( $tpl_name, $file_name ) // -- возвращает array( name => content ), умолчательная передаётся через "@"=>...
	{
		trace("[TemplateCompiler] ($tpl_name) parse $file_name");

		$content = $this->getTemplateContent($file_name);
		$res = $this->_explodeTemplate($tpl_name, $content);
		return $res;
	}

  function _TemplateCompile ($content) // -- компилирует "контент" одного шаблона
  {
    $content = preg_replace_callback($this->long_regexp, array( $this, "_TemplateCallback"), $content);
    return $content;
  }

  function _TemplateCallback ($things)
  {
		do {
			$thing = $things[0];

			// {{!!action param=value param=value value}}....{{/!!}}
			if (preg_match($this->double_regexp, $thing, $matches)) {
				$params = array();
				$params[0] = array(
					TE_TYPE => TE_TYPE_PHP_SCRIPT,
					TE_VALUE => 'ob_get_clean()'
				);
				$params = $this->_ParseActionParams( $matches[1], $params );

				$result = 'ob_start();?'.'>'.$this->_TemplateCompile($matches[2]).'<'."?php \n";
				$t = $this->compile_action($params);
				$result .= (isset($t['code']) ? $t['code'] : '').(isset($t['return']) ? "echo {$t['return']};\n" : '');
				break;
			}

      // 1. rip {{..}}
      preg_match($this->single_regexp, $thing, $matches);
			// убираем из {{}} переводы строки
      $thing = preg_replace('#[\\n\\r]#', '', $matches[1]);

      // 2. shortcuts
      foreach( $this->tpl->tpl_shortcuts as $shortcut=>$replacement ) {
        if (strpos($thing, $shortcut) === 0) {
          if (!is_array($replacement)) $replacement = array($replacement, "");
          $thing = $replacement[0]. substr($thing, strlen($shortcut)). $replacement[1];
        }
			}

      // {{?:}}
			if (strpos($thing, $this->tpl->tpl_construct_ifelse) === 0) {
				$c = strlen($this->tpl->tpl_construct_ifelse);
				$what = substr( $thing, $c );
				if ($what) {
					$result =  ' } elseif ('.$this->_parseExpr($what).') { ';
				} else {
					$result =  ' } else { ';
				}
				break;
			}

      // {{/?}}
      if ($thing == "/".$this->tpl->tpl_construct_if) {
        $result = ' } ';
				break;
      }

      // {{?var}}
      if (strpos($thing, $this->tpl->tpl_construct_if) === 0) {
        $what = substr($thing, strlen($this->tpl->tpl_construct_if));
        $result =  ' if ('.$this->_parseExpr($what).') { ';
				break;
      }

			// {{!action}}
      // {{#obj.property}}
      // {{var}}
      // {{var | action param | .. | action}}

			//проверяем палка-синтаксис
			$A = array_map('trim', explode('|',$thing));
			$var = $A[0]; unset($A[0]); // быстрее, чем $var = array_shift($A);
			$result = '';

			$type_script = '';
			if (preg_match('#^'.$this->object_regexp.'$#', $var, $matches)) {
				$script = $this->_ConstructGetValueScript($var);
				$type_script = "\$tpl->getType({$this->_phpString($var)})";
			} elseif ($t = $this->parse_action($var)) {
				$result .= isset($t['code']) ? $t['code'] : '';
				$script = isset($t['return']) ? $t['return'] : '';
			}

			if ($script) {

				if ($A) {
					// Палка-синтасксис {{variable | action | ..}}
					$result .= ' $_r = '.$script .';'."\n";
					$_c = sizeof($A); // в $A ключи начинаются с 1 из за unset($A[0])

					foreach ($A as $_k => $stmt) {
						$params = array();
						$params[0] = array(
								TE_TYPE => TE_TYPE_PHP_SCRIPT,
								TE_VALUE => '$_r'
								);
						if ($t = $this->parse_action($this->tpl->tpl_construct_action.$stmt, $params)) {
							if (isset($t['code'])) $result .= $t['code'];
							if (isset($t['return'])) {
								if ($_c != $_k) { // это не последняя палка
									$result .= "\$_r = {$t['return']};\n";
								} else {  // это последняя палка
									if (in_array($params[TEC_PARAM_NAME][TE_VALUE], array('escape', 'safe'))) {
										$result .= " echo {$t['return']};\n";
									} else {
										$result .= " echo \$tpl->convert({$t['return']});\n";
									}
								}
							} elseif ($_c != $_k) { // промежуточный элемет палки не возвращает результат -- глюк
								error('TemplateCompiler ['.$this->getCurrentTemplateName().']: parse error near: '.$thing . " can't use action as filter", 3);
							}
						} else {
							error('TemplateCompiler ['.$this->getCurrentTemplateName().']: parse error near: '.$thing, 3);
						}
					}
				} else { // лишь переменная {{variable}}
					$_result = $type_script
						? "<"."?=\$tpl->convert($script, $type_script);?".">\n"
						: "<"."?=$script;?".">\n";
				}
				break;
			}

			if ($result || $_result) break;

			error('TemplateCompiler ['.$this->getCurrentTemplateName().']: parse error near: '.$thing, 3);
		} while (false);

		if (isset($result)) $result = '<'.'?php '.$result.'?'.'>';
		if (isset($_result)) $result .= $_result;
		return $result;
  }

	public function parse_action ($thing, &$params=array())
	{
		$t = array();
		// match any action {{!any}}
		if (preg_match($this->action_regexp, $thing, $matches)) {
			$params = $this->_ParseActionParams( $matches[1], $params );
			$t = $this->compile_action($params);
		}
		return $t;
	} // end of method parse_action
	// {{!action param}}
	public function compile_action (&$params)
	{
		$name = $params[TEC_PARAM_NAME][TE_VALUE];
		$m = array($this, "compile_action_$name");
		if (!is_callable($m)) {
			if (preg_match('#[^[:alnum:]_]#', $name)) {
				$m = array($this, "compile_action_include");
			} else {
				$m = array($this, "compile_action_execute");
			}
		}
		$t = call_user_func($m, $params);
		return $t;
	} // end of method compile_action
	public function compile_action_include (&$params)
	{
		//$template = $this->_parseParam('@'.$params[TEC_PARAM_NAME][TE_VALUE]);
		$item_tpl = $this->_phpString($params[0][TE_VALUE]);
		unset($params[0]);
		// remove internal data
		unset($params[TEC_PARAM_NAME], $params[TEC_PARAM_PLAIN], $params[TEC_PARAM_CALLER]);

		$result = '';
		if ($params) $result .= "\$tpl->push();\n";
		foreach($params as $k => $v) {
			$php_key   = $this->_phpString($k);
			if ($v[TE_TYPE] === TE_TYPE_TEMPLATE) {
				$php_value = $this->_phpString($v[TE_VALUE]);
				$result .= "\$tpl->set({$php_key}, \$tpl->parse({$php_value}), \$tpl->content_type);\n";
			} elseif ($v[TE_TYPE] === TE_TYPE_VARIABLE) {
				$php_value = $this->_compileParam($v);
				$result .= "\$tpl->set({$php_key}, {$php_value}, \$tpl->getType({$php_key}));\n";
			} else {
				$php_value = $this->_compileParam($v);
				$result .= "\$tpl->set({$php_key}, {$php_value});\n";
			}
		}
		$result .= "echo \$tpl->parse({$item_tpl});\n";
		if ($params) $result .= "\$tpl->pop();\n";

		$t = array();
		$t['code'] = $result;
		return $t;
	} // end of method compile_action_include
	public function compile_action_execute (&$params)
	{
		$result = '';
		$param_contents = $this->_ImplodeActionParams( $params );
		$t = array();
		$t['code'] = '$_='.$param_contents.';';
		$t['return'] = '$tpl->Action('.$this->_compileParam($params[TEC_PARAM_NAME]).', $_ )';
		return $t;
	} // end of method compile_action_base
	public function compile_action_for (&$params)
	{
		$key = isset($params['each'])?$params['each']:$params[0]; // ключ
		//можно без each= а сразу, {{!for news do=@test.html:news}}
		$alias = isset($params['as'])?$params['as']:NULL; // алиас
		//можно {{!for news as='news_item' do=@test.html:news}}
		$item_tpl = isset($params['do'])?$params['do']:$params['use']; // ключ
		// разделитель (опционально)
		$sep_tpl = isset($params['sep'])?$params['sep']:NULL; // ключ
		// если список пуст (опционально)
		$empty_tpl = isset($params['empty'])?$params['empty']:NULL; // ключ


		$value = $this->_compileParam($key);


		if (isset($alias)) $item_store_to = $this->_compileParam($alias);
		else $item_store_to = '"*"';


		$item_tpl = $this->_phpString($item_tpl[TE_VALUE]);

		$c_init_sep_tpl = $c_echo_sep_tpl = '';
		if ($sep_tpl) {
			$sep_val = $this->_phpString($sep_tpl[TE_VALUE]);
			if ($sep_tpl[TE_TYPE] == TE_TYPE_TEMPLATE) {
				$c_init_sep_tpl = '$sep = $tpl->parse('.$sep_val.');';
				$c_echo_sep_tpl = 'echo $sep;';
			} else { // just string
				$c_init_sep_tpl = '$sep = '.$sep_val.';';
				$c_echo_sep_tpl = 'echo $sep;';
			}
		}

		$c_echo_empty_tpl = '';
		if ($empty_tpl) {
			$empty_tpl = $this->_phpString($empty_tpl[TE_VALUE]);
			$c_echo_empty_tpl = ' else { echo $tpl->parse('.$empty_tpl.'); }';
		}


		$result = ' $_z = '.$value .";\n";
		$result .= '
if(is_array($_z) && $_z) {
	$tpl->push();
	'.$c_init_sep_tpl.';

	$first = True;
	$num = 0;
	$count = count($_z);
	$for = array(
			"num"=>&$num,
			"count"=>&$count,
			"first"=>&$first,
		);
	$tpl->setRef("For", $for);
	$item_fn = $tpl->parseTemplate('.$item_tpl.');
	$_pc = $_nc = NULL;
	foreach($_z AS $k=>$r) {
		$num++;
		$for["key"] = $k;
		$for["odd"] = $num & 1;
		$for["even"] = ~$num & 1;
		$for["last"] = ($num === $count);
		$tpl->SetRef('.$item_store_to.', $r);

		$_c = $tpl->call($item_fn);
		$_nc = (bool)trim($_c);
		if ($_pc && $_nc) '.$c_echo_sep_tpl.';
		echo $_c;
		$_pc = $_nc;
		if ($first) { $first = false; }
	}
	$tpl->pop();
}
'.$c_echo_empty_tpl.'

unset($_z, $for, $item_fn);
				'."\n";
		$t = array();
		$t['code'] = $result;
		return $t;
	} // end of method compile_action_for

  function _matchConstructGetValueScript($matches)
  {
		$out = count($matches) == 3
			? $this->_ConstructGetValueScript($matches[2])
			: $matches[0];
	  return $out;
  }

  function _parseExpr($str)
  {
	  $func = array($this, '_matchConstructGetValueScript');

		// lucky: если не в кавычках
		$out = preg_replace_callback('#([\'"])(?(1).*?\\1)|\s+\\*\s+|'.$this->object_regexp.'#i', $func, $str );
	  return $out;
  }

	static $_compiled_keys = array();
  function _ConstructGetValueScript($key)
  {
		if (isset(self::$_compiled_keys[$key])) return self::$_compiled_keys[$key];
	  // lucky: FIXME: C&P from
		foreach( $this->tpl->tpl_shortcuts as $shortcut=>$replacement ) {
			if (strpos($key, $shortcut) === 0) {
				if (!is_array($replacement)) $replacement = array($replacement, "");
				$key = $replacement[0]. substr($key, strlen($shortcut)). $replacement[1];
			}
		}

	  if ($key{0} == '$') {
			// шаблонная переменная $*
		  $key = substr($key, 1);
	  }

		$names = explode($this->tpl->tpl_construct_object{1}, $key);
	  $res = (self::$_compiled_keys[$key] = $this->_ConstructGetValue($names, '$tpl->domain'));
		return $res;

  }
	function _ConstructGetValue($ks, $domain)
	{
	  $use_methods = True;

	  // lucky: если объект, можем вызвать геттер
		// можем сослаться на структуру внутри структуры struct.substruct.substruct
		// или так struct.getter().substruct
		//
		foreach ($ks as $i=>$k) {
			# HACK : 2008-04-07 : lucky
			# {{*}} благодаря $tpl->shortcuts порождает $*. , и $ks содержит ['*', '']
			# а должны воспринимать как ['*']
			#
			# if (!$k) нельзя т.к. $k === '0' м.б. для массивов {{items.0}}
			if ($k === "") continue;
			#

			// get array[key]
			$func = '';
			$args = array();
			if (preg_match('#^[[:digit:]]+$#', $k)) { // is numeric key ?
				$args[] = $k;
				$func .= 'a';
			} else {
				$args[] = "'".$k."'";
				$func .= 'a';
			}

			// для первого ключа $this->domain всегда array()
			if ($i !== 0) {
				// get object method or property
				if (preg_match('#^[A-Za-z_][A-Za-z0-9_]*$#', $k)) {
					//$args[] = "'".$k."'";
					$func .= 'p';
					if ($use_methods) {
						$method = ($this->tpl->tpl_construct_standard_getter_prefix
							? $this->tpl->tpl_construct_standard_getter_prefix .'_'.$k // get_$k
							: $k);
						if ($this->tpl->tpl_construct_standard_camelCase) {
							//$method = implode('', array_map('ucfirst', explode('_', $method))); // GetSomeValue
							$method = implode('', explode('_', $method)); // getsomevalue
						}
						if (preg_match('#^[A-Za-z_][A-Za-z0-9_]*$#', $k)) {
							$args[] = "'".$method."'";
							$func .= 'm';
						}
					}
				}
			}
			$domain = "\$tpl->{$func}({$domain},".implode(',', $args).")";
		}

		return $domain;
  }
  function _parseParam($thing)
  {
		do {
			// @template.html:name или @:name
			if ($thing{0} == '@') {
				$what = substr($thing, 1);
				if ($what{0} == ':') $what = $this->_template_name.'.html'.$what;
				$res = array(
					TE_TYPE => TE_TYPE_TEMPLATE,
					TE_VALUE => $what,
				);
				break;
			}

			if (preg_match($this->tpl_expr_regexp, $thing, $matches)) {
				$what = $matches[2];
				$res = array(
					TE_TYPE => TE_TYPE_EXPR,
					TE_VALUE => $what,
				);
				break;
			}

			// "just string"
			if (preg_match($this->tpl_string_regexp, $thing, $matches)) {
				$what = $matches[2];
				$res = array(
					TE_TYPE => TE_TYPE_STRING,
					TE_VALUE => $what,
				);
				break;
			}

			// integer -> string
			if (preg_match('/^(([-+])?[[:digit:]]+)$/', $thing, $matches)) {
				$what = $matches[1];
				$res = array(
					TE_TYPE => TE_TYPE_STRING,
					TE_VALUE => $what,
				);
				break;
			}

			if (preg_match($this->tpl_arg_regexp, $thing, $matches)) {
			// [[var]]
				$what = $matches[1];
				$res = array(
					TE_TYPE => TE_TYPE_VARIABLE,
					TE_VALUE => $what,
				);
				break;
			}

			// wats'up?
			// "just string"
		  $what = $thing;
		  $res = array(
			  TE_TYPE => TE_TYPE_STRING,
			  TE_VALUE => $what,
		  );

		} while (false);

	  return $res;
  }

  function _parseParams($content, $params=array())
  {
	  // 2. link`em back
	  // 3. get matches
	  $c = preg_match_all(
		  //1            2           34        5   6                       7
		  '/(^\s*|\s+)(?:([^=\s]+)=)?(([(])?("|\')?(.*?)(?(5)\\5)(?(4)[)]))($|(?=\s))/i',
                         $content, $matches, PREG_SET_ORDER  );
    // 4. sort out
    $named = array();
    foreach( $matches as $match ) {
      if ($match[2]) // named parameter
        $named[ $match[2] ] = $this->_parseParam($match[3]);
      elseif ($match[3]) // unnamed parameter
        $params[] = $this->_parseParam($match[3]);
    }
		$params += $named;
    return $params;
  }

  function _compileParam($param)
  {
	  switch ($param[TE_TYPE]) {
	  case     TE_TYPE_STRING: $res = $this->_phpString($param[TE_VALUE]); break;
	  case       TE_TYPE_EXPR: $res = $this->_parseExpr($param[TE_VALUE]); break;
	  case   TE_TYPE_VARIABLE: $res = $this->_ConstructGetValueScript($param[TE_VALUE]); break;
	  case   TE_TYPE_TEMPLATE: $res = $this->_phpString('@'.$param[TE_VALUE]); break;
	  case TE_TYPE_PHP_SCRIPT: $res = $param[TE_VALUE]; break;
	                  default: error('Unknown type'); break;
	  }
	  return $res;
  }

  function _ParseActionParams( $content, $params=array())
  {
    // 1. get name by explosion
    $a = explode(" ", $content);
		$name = $a[0]; unset($a[0]); // быстрее, чем $name = array_shift($a);
		if ($a) {
			$_content = " ".implode(" ", $a);
			$params = $this->_parseParams($_content, $params);
		}
    $params[ TEC_PARAM_PLAIN] = $this->_parseParam('"'.$content.'"');
    $params[  TEC_PARAM_NAME] = $this->_parseParam('"'.$name.'"'); // kuso@npj removed: strtolower($a[0]);
    $params[TEC_PARAM_CALLER] = $this->_parseParam('"'.$this->_template_name.'"');
	 return $params;
  }

  function _phpString($what)
  {
	  $res = '"'.
					 str_replace( "\"", "\\\"",
					 str_replace( "\$", "\\\$",
					 str_replace( "\n", "\\n",
					 str_replace( "\r", "",
					 str_replace( "\\", "\\\\", $what ))))).'"';
	  return $res;
  }

	function _ImplodeActionParams( $params )
	{
		// remove internal data
		unset($params[TEC_PARAM_NAME], $params[TEC_PARAM_PLAIN], $params[TEC_PARAM_CALLER]);

		$args = array();
		foreach( $params as $k=>$v ) {
			$what = $this->_compileParam($v);
			$key  = is_numeric($k) ? $k : $this->_phpString($k);
			$args[] = "$key => $what";

		}
		$list = implode(",\n", $args);
		return "array($list)";
	}

  /**
	* строит тело функции вокруг $content, включая { }
	*/
  function _TemplateBuildFunction( $content )
  {
		$content = "{ ?".">\n".$content."\n<"."?php }";
		return $content;
  }


  //zharik - проверяет можем ли мы писать в этот файл
  function _CheckWritable($file_to)
  {
    if (file_exists($file_to) && !is_writable($file_to) )
     error( "TPL::_CheckWritable: No access to -> ". $file_to );

    if (!file_exists($file_to) && !is_writable($this->tpl->tpl_cache_dir))
     error( "TPL::_CheckWritable: No access to entire cache dir -> ". $this->tpl->tpl_cache_dir );
  }

	public function output_filter ($content)
	{
		foreach ($this->ep_output_filter as $m) {
			$content = call_user_func($m, $content, $this);
		}
		return $content;
	} // end of function output_filter

	public function register_plugins ($config)
	{
		foreach ($config as $v) {
			$m = array($this, 'register_plugin_'.$v[0]);
			if (is_callable($m)) call_user_func($m, $v);
		}
	} // end of function register_plugins

	public $ep_output_filter = array();
	public function register_plugin_output_filter ($config)
	{
		require_once $this->tpl->findFile('plugins', $config[1], NULL, 'php');
		$callable = 'tpl_'.$config[1];
		if (is_callable($callable)) {
			$this->ep_output_filter[] = $callable;
		}
	} // end of function register_output_filter

// EOC{ TemplateEngineCompiler }
}

